package cn.dac.configuration;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import cn.dac.shiro.RetryLimitHashedCredentialsMatcher;
import cn.dac.shiro.cache.RedisCacheManager;
import cn.dac.shiro.filter.KickoutSessionControlFilter;
import cn.dac.shiro.filter.URLPermissionsFilter;
import cn.dac.shiro.realm.UserRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfiguration {


    private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);

    /** Shiro的Web过滤器 **/
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        logger.debug("###【注入Web过滤器】shiroFilterFactoryBean", ShiroFilterFactoryBean.class);
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        //自定义过滤器
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();//获取filters
        //验证码过滤器
        //filters.put("kaptcha", new CustomFormAuthenticationFilter());
        //限制同一帐号同时在线的个数。
        filters.put("kickout", new KickoutSessionControlFilter(redisCacheManager(),sessionManager()));
        //访问权限
        filters.put("perms", urlPermissionsFilter());
        shiroFilterFactoryBean.setFilters(filters);//交给spring shiroFilter管理


        //Shiro的核心安全接口,这个属性是必须的
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        //登录成功后要跳转的连接,逻辑也可以自定义，例如返回上次请求的页面
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //用户访问未对其授权的资源时,所显示的连接
        shiroFilterFactoryBean.setUnauthorizedUrl("/pages/403");

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");

        // 过滤链定义，从上向下顺序执行，一般将 /**放在最为下边
        // anon:匿名访问
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/kaptcha", "anon");
        filterChainDefinitionMap.put("/kickout","anon");

        filterChainDefinitionMap.put("/api/**","anon");

        //user:配置记住我或认证通过可以访问的地址
        filterChainDefinitionMap.put("/index", "user");
        filterChainDefinitionMap.put("/", "user");

        //authc:必须认证通过才可以访问
        filterChainDefinitionMap.put("/login", "kickout,authc");
        filterChainDefinitionMap.put("/**", "kickout,authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }
    @Bean
    public URLPermissionsFilter urlPermissionsFilter() {
        return new URLPermissionsFilter();
    }
    /** 核心管理器**/
    @Bean
    public SecurityManager securityManager() {
        logger.debug("###【注入核心管理器】securityManager", ShiroFilterFactoryBean.class);
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置Realm，用于获取认证凭证
        securityManager.setRealm(userRealm());

        /*
        //注入EhCacheManager缓存管理器
        securityManager.setCacheManager(ehCacheManager());
        */


        //注入sessionManager redisCacheManager
        securityManager.setSessionManager(sessionManager());
        securityManager.setCacheManager(redisCacheManager());
        //注入Cookie(记住我)管理器(remenberMeManager)
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

    @Bean
    public UserRealm userRealm() {
        UserRealm userRealm = new UserRealm();
        //告诉realm,使用credentialsMatcher加密算法类来验证密文
        userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        //userRealm.setCachingEnabled(false);


        //启用缓存,默认false
        userRealm.setCachingEnabled(true);
        //  启用身份验证缓存，即缓存AuthenticationInfo信息，默认false；
        userRealm.setAuthenticationCachingEnabled(true);
        //  缓存AuthenticationInfo信息的缓存名称,即配置在ehcache.xml中的cache name
        userRealm.setAuthenticationCacheName("authenticationCache");
        //  启用授权缓存，即缓存AuthorizationInfo信息，默认false；
        userRealm.setAuthorizationCachingEnabled(true);
        //  缓存AuthorizationInfo信息的缓存名称；
        userRealm.setAuthorizationCacheName("authorizationCache");
        return userRealm;
    }

    /** 凭证匹配器 **/
    @Bean(name="credentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        //HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //密码错误次数限制
        HashedCredentialsMatcher hashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher(redisCacheManager());

        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2);//散列的次数，比如散列两次，相当于 md5(md5(""));
        //storedCredentialsHexEncoded默认是true，此时用的是密码加密用的是Hex编码；false时用Base64编码
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);

        return hashedCredentialsMatcher;
    }
    /** Shiro生命周期处理器 **/
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }
    @Bean(name = "shiroDialect")
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }


    /** 缓存 Ehcache*/
    /*@Bean(name="sessionManager")
    public DefaultWebSessionManager defaultWebSessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setCacheManager(ehCacheManager());
        sessionManager.setGlobalSessionTimeout(1800000);
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setDeleteInvalidSessions(true);
        return sessionManager;
    }
    @Bean
    public EhCacheManager ehCacheManager() {
        logger.info("###【注入缓存管理器】ehCacheManager", EhCacheManager.class);
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:ehcache/ehcache.xml");
        return cacheManager;
    }*/

    /** 缓存管理器 **/
    @Bean
    public RedisCacheManager redisCacheManager() {
        return new RedisCacheManager();
    }
    /** 会话ID生成器  **/
    @Bean
    public JavaUuidSessionIdGenerator sessionIdGenerator(){
        return new JavaUuidSessionIdGenerator();
    }

    /** 会话DAO **/
    @Bean
    public EnterpriseCacheSessionDAO sessionDAO(){
        //return new RedisSessionDAO();
        EnterpriseCacheSessionDAO enterCacheSessionDAO = new EnterpriseCacheSessionDAO();
        //enterCacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
        //enterCacheSessionDAO.setCacheManager(redisCacheManager());
        enterCacheSessionDAO.setSessionIdGenerator(sessionIdGenerator());
        return enterCacheSessionDAO;
    }
    /** 会话管理器 **/
    @Bean
    public SessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //ShiroSessionManager解决频繁调用redis
        //ShiroSessionManager sessionManager = new ShiroSessionManager();
        logger.debug("###【注入sessionDAO】:"+sessionDAO());
        sessionManager.setSessionDAO(sessionDAO());
        sessionManager.setGlobalSessionTimeout(1800000);//session超时时间 默认半个小时
        sessionManager.setCacheManager(redisCacheManager());
        // 定时清理失效会话, 清理用户直接关闭浏览器造成的孤立会话
        sessionManager.setSessionValidationInterval(1800);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        //去掉连接中生成的JSESSIONID
        sessionManager.setSessionIdUrlRewritingEnabled(false);

        sessionManager.setSessionIdCookieEnabled(true);
        sessionManager.setSessionIdCookie(sessionIdCookie());
        return sessionManager;
    }
    /** 会话Cookie模板 **/
    @Bean(name = "sessionIdCookie")
    public SimpleCookie sessionIdCookie() {
        SimpleCookie cookie = new SimpleCookie("shiroSesssion");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(-1);
        return cookie;
    }

    /** rememberMe管理器 **/
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        logger.debug("###【注入记住我Cookie】rememberMeManager", CookieRememberMeManager.class);
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        //rememberme cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度（128 256 512 位），通过以下代码可以获取
        //KeyGenerator keygen = KeyGenerator.getInstance("AES");
        //SecretKey deskey = keygen.generateKey();
        //System.out.println(Base64.encodeToString(deskey.getEncoded()));
        byte[] cipherKey = Base64.decode("TwD4WA6Hvrho+Lf1d04WWw==");
        cookieRememberMeManager.setCipherKey(cipherKey);
        cookieRememberMeManager.setCookie(rememberMeCookie());
        return cookieRememberMeManager;
    }
    @Bean
    public SimpleCookie rememberMeCookie(){
        //这个参数是cookie的名称，对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //如果httyOnly设置为true，则客户端不会暴露给客户端脚本代码，使用HttpOnly cookie有助于减少某些类型的跨站点脚本攻击；
        simpleCookie.setHttpOnly(true);
        //记住我cookie生效时间,默认30天 ,单位秒：60 * 60 * 24 * 30
        simpleCookie.setMaxAge(259200);

        return simpleCookie;
    }



}
